1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import java.applet.Applet;
34 import java.awt.Image;
35 import java.awt.Graphics;
36 import java.awt.Dimension;
37 import java.awt.event.MouseEvent;
38 import java.awt.event.MouseListener;
39 import java.awt.event.MouseMotionListener;
40 import java.net.URL;
41 import java.awt.image.IndexColorModel;
42 import java.awt.image.MemoryImageSource;
43 import java.io.BufferedReader;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.StreamTokenizer;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52
53
54
55
56
57
58
59 final class XYZChemModel {
60
61 float vert[];
62 Atom atoms[];
63 int tvert[];
64 int ZsortMap[];
65 int nvert, maxvert;
66 static final Map<String, Atom> atomTable = new HashMap<String, Atom>();
67 static Atom defaultAtom;
68
69 static {
70 atomTable.put("c", new Atom(0, 0, 0));
71 atomTable.put("h", new Atom(210, 210, 210));
72 atomTable.put("n", new Atom(0, 0, 255));
73 atomTable.put("o", new Atom(255, 0, 0));
74 atomTable.put("p", new Atom(255, 0, 255));
75 atomTable.put("s", new Atom(255, 255, 0));
76 atomTable.put("hn", new Atom(150, 255, 150));
77 defaultAtom = new Atom(255, 100, 200);
78 }
79 boolean transformed;
80 Matrix3D mat;
81 float xmin, xmax, ymin, ymax, zmin, zmax;
82
83 XYZChemModel() {
84 mat = new Matrix3D();
85 mat.xrot(20);
86 mat.yrot(30);
87 }
88
89
90 XYZChemModel(InputStream is) throws Exception {
91 this();
92 StreamTokenizer st = new StreamTokenizer(
93 new BufferedReader(new InputStreamReader(is, "UTF-8")));
94 st.eolIsSignificant(true);
95 st.commentChar('#');
96
97 try {
98 scan:
99 while (true) {
100 switch (st.nextToken()) {
101 case StreamTokenizer.TT_EOF:
102 break scan;
103 default:
104 break;
105 case StreamTokenizer.TT_WORD:
106 String name = st.sval;
107 double x = 0,
108 y = 0,
109 z = 0;
110 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
111 x = st.nval;
112 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
113 y = st.nval;
114 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
115 z = st.nval;
116 }
117 }
118 }
119 addVert(name, (float) x, (float) y, (float) z);
120 while (st.ttype != StreamTokenizer.TT_EOL
121 && st.ttype != StreamTokenizer.TT_EOF) {
122 st.nextToken();
123 }
124
125 }
126
127 }
128
129 is.close();
130
131 }
132 catch (IOException e) {
133 }
134
135 if (st.ttype != StreamTokenizer.TT_EOF) {
136 throw new Exception(st.toString());
137 }
138
139 }
140
141
142 int addVert(String name, float x, float y, float z) {
143 int i = nvert;
144 if (i >= maxvert) {
145 if (vert == null) {
146 maxvert = 100;
147 vert = new float[maxvert * 3];
148 atoms = new Atom[maxvert];
149 } else {
150 maxvert *= 2;
151 float nv[] = new float[maxvert * 3];
152 System.arraycopy(vert, 0, nv, 0, vert.length);
153 vert = nv;
154 Atom na[] = new Atom[maxvert];
155 System.arraycopy(atoms, 0, na, 0, atoms.length);
156 atoms = na;
157 }
158 }
159 Atom a = atomTable.get(name.toLowerCase());
160 if (a == null) {
161 a = defaultAtom;
162 }
163 atoms[i] = a;
164 i *= 3;
165 vert[i] = x;
166 vert[i + 1] = y;
167 vert[i + 2] = z;
168 return nvert++;
169 }
170
171
172 void transform() {
173 if (transformed || nvert <= 0) {
174 return;
175 }
176 if (tvert == null || tvert.length < nvert * 3) {
177 tvert = new int[nvert * 3];
178 }
179 mat.transform(vert, tvert, nvert);
180 transformed = true;
181 }
182
183
184
185
186
187 void paint(Graphics g) {
188 if (vert == null || nvert <= 0) {
189 return;
190 }
191 transform();
192 int v[] = tvert;
193 int zs[] = ZsortMap;
194 if (zs == null) {
195 ZsortMap = zs = new int[nvert];
196 for (int i = nvert; --i >= 0;) {
197 zs[i] = i * 3;
198 }
199 }
200
201
202
203
204
205
206
207
208 for (int i = nvert - 1; --i >= 0;) {
209 boolean flipped = false;
210 for (int j = 0; j <= i; j++) {
211 int a = zs[j];
212 int b = zs[j + 1];
213 if (v[a + 2] > v[b + 2]) {
214 zs[j + 1] = a;
215 zs[j] = b;
216 flipped = true;
217 }
218 }
219 if (!flipped) {
220 break;
221 }
222 }
223
224 int lim = nvert;
225 if (lim <= 0 || nvert <= 0) {
226 return;
227 }
228 for (int i = 0; i < lim; i++) {
229 int j = zs[i];
230 int grey = v[j + 2];
231 if (grey < 0) {
232 grey = 0;
233 }
234 if (grey > 15) {
235 grey = 15;
236 }
237
238 atoms[j / 3].paint(g, v[j], v[j + 1], grey);
239
240
241 }
242 }
243
244
245 void findBB() {
246 if (nvert <= 0) {
247 return;
248 }
249 float v[] = vert;
250 float _xmin = v[0], _xmax = _xmin;
251 float _ymin = v[1], _ymax = _ymin;
252 float _zmin = v[2], _zmax = _zmin;
253 for (int i = nvert * 3; (i -= 3) > 0;) {
254 float x = v[i];
255 if (x < _xmin) {
256 _xmin = x;
257 }
258 if (x > _xmax) {
259 _xmax = x;
260 }
261 float y = v[i + 1];
262 if (y < _ymin) {
263 _ymin = y;
264 }
265 if (y > _ymax) {
266 _ymax = y;
267 }
268 float z = v[i + 2];
269 if (z < _zmin) {
270 _zmin = z;
271 }
272 if (z > _zmax) {
273 _zmax = z;
274 }
275 }
276 this.xmax = _xmax;
277 this.xmin = _xmin;
278 this.ymax = _ymax;
279 this.ymin = _ymin;
280 this.zmax = _zmax;
281 this.zmin = _zmin;
282 }
283 }
284
285
286
287 @SuppressWarnings("serial")
288 public class XYZApp extends Applet implements Runnable, MouseListener,
289 MouseMotionListener {
290
291 XYZChemModel md;
292 boolean painted = true;
293 float xfac;
294 int prevx, prevy;
295 float scalefudge = 1;
296 Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
297 String mdname = null;
298 String message = null;
299 Image backBuffer;
300 Graphics backGC;
301 Dimension backSize;
302
303 private synchronized void newBackBuffer() {
304 backBuffer = createImage(getSize().width, getSize().height);
305 if (backGC != null) {
306 backGC.dispose();
307 }
308 backGC = backBuffer.getGraphics();
309 backSize = getSize();
310 }
311
312 @Override
313 public void init() {
314 mdname = getParameter("model");
315 try {
316 scalefudge = Float.valueOf(getParameter("scale")).floatValue();
317 } catch (Exception ignored) {
318 }
319 amat.yrot(20);
320 amat.xrot(20);
321 if (mdname == null) {
322 mdname = "model.obj";
323 }
324 resize(getSize().width <= 20 ? 400 : getSize().width,
325 getSize().height <= 20 ? 400 : getSize().height);
326 newBackBuffer();
327 addMouseListener(this);
328 addMouseMotionListener(this);
329 }
330
331 @Override
332 public void destroy() {
333 removeMouseListener(this);
334 removeMouseMotionListener(this);
335 }
336
337 @Override
338 public void run() {
339 InputStream is = null;
340 try {
341 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
342 is = new URL(getDocumentBase(), mdname).openStream();
343 XYZChemModel m = new XYZChemModel(is);
344 Atom.setApplet(this);
345 md = m;
346 m.findBB();
347 float xw = m.xmax - m.xmin;
348 float yw = m.ymax - m.ymin;
349 float zw = m.zmax - m.zmin;
350 if (yw > xw) {
351 xw = yw;
352 }
353 if (zw > xw) {
354 xw = zw;
355 }
356 float f1 = getSize().width / xw;
357 float f2 = getSize().height / xw;
358 xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
359 } catch (Exception e) {
360 Logger.getLogger(XYZApp.class.getName()).log(Level.SEVERE, null, e);
361 md = null;
362 message = e.toString();
363 }
364 try {
365 if (is != null) {
366 is.close();
367 }
368 } catch (Exception ignored) {
369 }
370 repaint();
371 }
372
373 @Override
374 public void start() {
375 if (md == null && message == null) {
376 new Thread(this).start();
377 }
378 }
379
380 @Override
381 public void stop() {
382 }
383
384
385 @Override
386 public void mouseClicked(MouseEvent e) {
387 }
388
389 @Override
390 public void mousePressed(MouseEvent e) {
391 prevx = e.getX();
392 prevy = e.getY();
393 e.consume();
394 }
395
396 @Override
397 public void mouseReleased(MouseEvent e) {
398 }
399
400 @Override
401 public void mouseEntered(MouseEvent e) {
402 }
403
404 @Override
405 public void mouseExited(MouseEvent e) {
406 }
407
408 @Override
409 public void mouseDragged(MouseEvent e) {
410 int x = e.getX();
411 int y = e.getY();
412 tmat.unit();
413 float xtheta = (prevy - y) * (360.0f / getSize().width);
414 float ytheta = (x - prevx) * (360.0f / getSize().height);
415 tmat.xrot(xtheta);
416 tmat.yrot(ytheta);
417 amat.mult(tmat);
418 if (painted) {
419 painted = false;
420 repaint();
421 }
422 prevx = x;
423 prevy = y;
424 e.consume();
425 }
426
427 @Override
428 public void mouseMoved(MouseEvent e) {
429 }
430
431 @Override
432 public void update(Graphics g) {
433 if (backBuffer == null) {
434 g.clearRect(0, 0, getSize().width, getSize().height);
435 }
436 paint(g);
437 }
438
439 @Override
440 public void paint(Graphics g) {
441 if (md != null) {
442 md.mat.unit();
443 md.mat.translate(-(md.xmin + md.xmax) / 2,
444 -(md.ymin + md.ymax) / 2,
445 -(md.zmin + md.zmax) / 2);
446 md.mat.mult(amat);
447
448 md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
449 md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
450 md.transformed = false;
451 if (backBuffer != null) {
452 if (!backSize.equals(getSize())) {
453 newBackBuffer();
454 }
455 backGC.setColor(getBackground());
456 backGC.fillRect(0, 0, getSize().width, getSize().height);
457 md.paint(backGC);
458 g.drawImage(backBuffer, 0, 0, this);
459 } else {
460 md.paint(g);
461 }
462 setPainted();
463 } else if (message != null) {
464 g.drawString("Error in model:", 3, 20);
465 g.drawString(message, 10, 40);
466 }
467 }
468
469 private synchronized void setPainted() {
470 painted = true;
471 notifyAll();
472 }
473
474 @Override
475 public String getAppletInfo() {
476 return "Title: XYZApp \nAuthor: James Gosling \nAn applet to put"
477 + " a Chemical model into a page.";
478 }
479
480 @Override
481 public String[][] getParameterInfo() {
482 String[][] info = {
483 { "model", "path string", "The path to the model to be displayed"
484 + " in .xyz format "
485 + "(see http://chem.leeds.ac.uk/Project/MIME.html)."
486 + " Default is model.obj." },
487 { "scale", "float", "Scale factor. Default is 1 (i.e. no scale)." }
488 };
489 return info;
490 }
491 }
492
493
494 class Atom {
495
496 private static Applet applet;
497 private static byte[] data;
498 private final static int R = 40;
499 private final static int hx = 15;
500 private final static int hy = 15;
501 private final static int bgGrey = 192;
502 private final static int nBalls = 16;
503 private static int maxr;
504 private int Rl;
505 private int Gl;
506 private int Bl;
507 private Image balls[];
508
509 static {
510 data = new byte[R * 2 * R * 2];
511 int mr = 0;
512 for (int Y = 2 * R; --Y >= 0;) {
513 int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5);
514 int p = Y * (R * 2) + R - x0;
515 for (int X = -x0; X < x0; X++) {
516 int x = X + hx;
517 int y = Y - R + hy;
518 int r = (int) (Math.sqrt(x * x + y * y) + 0.5);
519 if (r > mr) {
520 mr = r;
521 }
522 data[p++] = r <= 0 ? 1 : (byte) r;
523 }
524 }
525 maxr = mr;
526 }
527
528 static void setApplet(Applet app) {
529 applet = app;
530 }
531
532 Atom(int Rl, int Gl, int Bl) {
533 this.Rl = Rl;
534 this.Gl = Gl;
535 this.Bl = Bl;
536 }
537
538 private int blend(int fg, int bg, float fgfactor) {
539 return (int) (bg + (fg - bg) * fgfactor);
540 }
541
542 private void Setup() {
543 balls = new Image[nBalls];
544 byte red[] = new byte[256];
545 red[0] = (byte) bgGrey;
546 byte green[] = new byte[256];
547 green[0] = (byte) bgGrey;
548 byte blue[] = new byte[256];
549 blue[0] = (byte) bgGrey;
550 for (int r = 0; r < nBalls; r++) {
551 float b = (float) (r + 1) / nBalls;
552 for (int i = maxr; i >= 1; --i) {
553 float d = (float) i / maxr;
554 red[i] = (byte) blend(blend(Rl, 255, d), bgGrey, b);
555 green[i] = (byte) blend(blend(Gl, 255, d), bgGrey, b);
556 blue[i] = (byte) blend(blend(Bl, 255, d), bgGrey, b);
557 }
558 IndexColorModel model = new IndexColorModel(8, maxr + 1,
559 red, green, blue, 0);
560 balls[r] = applet.createImage(
561 new MemoryImageSource(R * 2, R * 2, model, data, 0, R * 2));
562 }
563 }
564
565 void paint(Graphics gc, int x, int y, int r) {
566 Image ba[] = balls;
567 if (ba == null) {
568 Setup();
569 ba = balls;
570 }
571 Image i = ba[r];
572 int size = 10 + r;
573 gc.drawImage(i, x - (size >> 1), y - (size >> 1), size, size, applet);
574 }
575 }